#include "DBFile.h"

#include <string.h>
#include <cstdlib>
#include <iostream>
#include <ctime>
#include <math.h>

// Constants...
#define FTYPE_STRING_SIZE 3
#define CREATE_FILE 0
#define OPEN_FILE 1

// Error Constants
#define INVALID_FTYPE -1

using namespace std;

DBFile::DBFile (){}
DBFile::DataFile::DataFile(){ }
DBFile::HeapFile::HeapFile(){ dbType = heap;  }
DBFile::SortFile::SortFile(){ dbType = sorted; writeEnabled = false; }
DBFile::TreeFile::TreeFile(){ dbType = tree;  }

DBFile::DataFile::~DataFile(){ }
DBFile::HeapFile::~HeapFile(){ }
DBFile::SortFile::~SortFile(){ }
DBFile::TreeFile::~TreeFile(){ }



void DBFile::MoveFirst(){
     dataFile->MoveFirst();
}
void DBFile::HeapFile::MoveFirst(){
     //Retrieve the First Real Page
     file.GetPage(currentPg, 0);
}
void DBFile::SortFile::MoveFirst(){
	if(writeEnabled) BigQShutdown();
	file.GetPage(currentPg, 0);
}
void DBFile::TreeFile::MoveFirst(){
	exit(1);
}


void DBFile::Add(Record &addMe){
	 dataFile->Add(addMe);
}
int DBFile::HeapFile::Add(Record &addMe){
	 Record temp;
	 // need to consume this so addMe is empty.
	 temp.Consume(&addMe);
	 if((*currentPg).Append(&temp)) return 1;
	 else return TryAddPageAndRecord(temp);
}
int DBFile::SortFile::Add(Record &addMe){
	 if(!writeEnabled) BigQSetup();
	 sorter->AddToPipe(&addMe);
}
int DBFile::SortFile::TryAddPageAndRecord(Record *addMe){
	file.AddPage(currentPg, pgCount++);
	currentPg = new Page();
	return currentPg->Append(addMe);
}

int DBFile::TreeFile::Add(Record &addMe){
	exit(1);
}


int DBFile::GetNext(Record &fetchMe){
	 return dataFile->GetNext(fetchMe);
}
int DBFile::DataFile::Next(Record &fetchMe){
	return  (currentPg->GetFirst(&fetchMe))? 1 : AttemptGetOnNextPage(fetchMe);
}
int DBFile::DataFile::AttemptGetOnNextPage(Record &fetchMe){
 	 if(currentPgNum >= (file.GetLength()- 1)) return 0;
     else{
          file.GetPage(currentPg, currentPgNum++);
	      return Next(fetchMe);
     }
}
int DBFile::HeapFile::GetNext(Record &fetchMe){
	 return DataFile::Next(fetchMe);
}
int DBFile::SortFile::GetNext(Record &fetchMe){
	if(writeEnabled) BigQShutdown();
	return DataFile::Next(fetchMe);
}
int DBFile::TreeFile::GetNext(Record &fetchMe){
     exit(1);
}


int DBFile::GetNext(Record &fetchMe, CNF &applyMe, Record &literal){
     return(dataFile->GetNext(fetchMe, applyMe, literal));
}
int DBFile::HeapFile::GetNext(Record &fetchMe, CNF &applyMe, Record &literal){
	 ComparisonEngine ce;
	 int check = 0;
	 while(!check){
	      if(!GetNext(fetchMe)) return 0;
	           else ((check = ce.Compare(&fetchMe, &literal, &applyMe)));
	      }
	 return check;
}
int DBFile::SortFile::GetNext(Record &fetchMe, CNF &applyMe, Record &literal){
	if(writeEnabled) BigQShutdown();
	ComparisonEngine ce;
	 int check = 0;
	 while(!check){
		  if(!GetNext(fetchMe)) return 0;
			   else ((check = ce.Compare(&fetchMe, &literal, &applyMe)));
		  }
	 return check;
    // Need to look @ the CNF to build up a query...
//	if(query != NULL)
//		return (query->numAtts == 0)? DataFile::Next(fetchMe): BinarySearch(&fetchMe, literal, false);
//	else{
//		MoveFirst();
//		query = new OrderMaker();
//		BuildQuery(applyMe);
//		return (query->numAtts == 0)? DataFile::Next(fetchMe): BinarySearch(&fetchMe, literal, true);
//	}
}
void DBFile::SortFile::BuildQuery(CNF &applyMe){
	cout << "BuildQuery" << endl;
	// We will compare our CNF with our current sortOrder, and build up our query
	applyMe.BuildSortQuery(sortOrder, query);
//  Code for ^ ^ ^ ^ ^ ^ ^ is below, just in case we need to do a copy for the next phase of the project...
//	int i = 0;
//	// Creating query...
//	for(i; i < MAX_ANDS; i++){
//		int prevAtts = query.numAtts;
//		for (int j = 0; j < MAX_ANDS;j++){
//			if((applyMe.orlist[j][0].whichAtt1 == sortORder->whichAtts[i])  // If we find the att in the CNF...
//				and(applyMe.orLens[j] == 1)                                 // And it's the only one...
//					and(orList[j][0].op == Equals)) {                        // And its an equality...
//						query.whichAtts[i] = applyMe.orlist[j][0];         // Then we add it to our query OrderMaker.
//						query.whichTypes[i] = applyMe.orlist[j][0].attType;
//						query.numAtts++;
//						break;
//					}
//		}
//		// If the attribute was not found in the CNF, then we stop the process...
//		if(query.numAtts == prevAtts) break;
//	}
}
int DBFile::SortFile::BinarySearch(Record *fetchMe, Record &literal, bool start){
	// First thing's first... Must place ALL records in some datastructure... prolly an array
	found = false;
	fail = false;
	int ternary;
	bool startPage = true;
	Record temp;
	if(start){ firstPageNum = 1; lastPageNum = file.GetLength() -2; searchPage = new Page();}
	while(not found and not fail){
		if(firstPageNum < lastPageNum){ // Then we are working across multiple pages... ( 1 and 3, but not 1 and 2)
			cout << "Binary Search Across Multiple Pages" << endl;
			cout << firstPageNum << "<-- First ||| Last -->" << lastPageNum <<endl;
			if(firstPageNum + 1 < lastPageNum){
				cout << "Searching for Page" << endl;
				currentPageNum = (int)ceil(firstPageNum + lastPageNum)/2;
				cout << "Current: " << currentPageNum << endl;
				file.GetPage(searchPage, currentPageNum);
				cout << "SEGFAULT PASSED" << endl;
				if(searchPage->GetNumRecs() == 0){
					cout << "Search Page is Empty" << endl;
					ternary = 1;
				}
				else{
					cout << "Number of records: "<< searchPage->GetNumRecs() << endl;
					cout << "Search Page Not Empty" << endl;
					temp.Copy(searchPage->myRecs->Current(0));
					cout << "Grabbed temp record" << endl;
					ternary = ce.Compare(&temp, &literal, query);
				}
				if(ternary == -1) firstPageNum = currentPageNum;
				else if(ternary == 1) lastPageNum = currentPageNum;
				else{
					cout << " Record Found!" << endl;
					found = true; // We have found it!!
					fetchMe->Consume(&temp);
					return 1;
				}
			}else{ // Then the difference between them is 1
				cout << "Last Page check: First and last pages are next to each other" << endl;
				int current1 = (int)ceil((firstPageNum + lastPageNum)/2);
				int current2 = (firstPageNum + lastPageNum) >> 1;
				cout << "Current: " << current1 << endl;
				file.GetPage(searchPage, current1);
				if(searchPage->GetNumRecs() == 0){
					cout << "Search Failed: Last Page is Empty" << endl;
					fail = true;
					return 0;
				}else{
					temp.Copy(searchPage->myRecs->Current(0));
					ternary = ce.Compare(&temp, &literal, query);
				}
				if(ternary == -1)firstPageNum = (currentPageNum = current1);
				else if (ternary == 1) lastPageNum = (currentPageNum = current2);
				else{
					cout << "Record Found!" << endl;
					found = true;
					fetchMe->Consume(&temp);
					return 1;
				}
			}
		}else{ // We are working within a single page...
			cout << "Binary Search Within single page" << endl;
			if(start){
				cout << "First Page Num:" << firstPageNum << endl;
				file.GetPage(searchPage, firstPageNum);
			}
			if(startPage){ firstRecordNum = 0; lastRecordNum = searchPage->GetNumRecs()-1; start = false;}
			if(searchPage->GetNumRecs() == 0){
				cout << "Search Failed: Search Page has no records" << endl;
				fail = true;
				return 0;
			}
			if(firstRecordNum + 1 == lastRecordNum){ // This is a special case last check....
				cout << "Last check: first and last record are next to each other..." << endl;
				temp.Copy(searchPage->myRecs->Current(firstRecordNum));
				if(ce.Compare(&temp, &literal, query) != 0){
					temp.Copy(searchPage->myRecs->Current(lastRecordNum));
					if(ce.Compare(&temp, &literal, query) != 0){
						cout << "Search Failed: No Records match literal" << endl;
						fail = true;
					}else{
						cout << "Matching Record Found" << endl;
						found = true;
						currentRecordNum = lastRecordNum;
						fetchMe->Consume(&temp);
						return 1;
					}
				}else{
					cout << "Matching Record found" << endl;
					found = true;
					currentRecordNum = firstRecordNum;
					fetchMe->Consume(&temp);
					return 1;
				}
			}else if(firstRecordNum == lastRecordNum){ // First and last are equal... so this is last check...
				cout << "Last Check: One file to go..." << endl;
				temp.Copy(searchPage->myRecs->Current(firstRecordNum));
				if(ce.Compare(&temp, &literal, query) != 0) fail = true;
				else{
					cout << "Matching Record Found" << endl;
					found = true;
					currentRecordNum = firstRecordNum;
					fetchMe->Consume(&temp);
					return 1;
				}
			}else{ // General Case
				cout << "Searching..." << endl;
				file.GetPage(searchPage, currentPageNum);
				currentRecordNum = (int)ceil((firstRecordNum + lastRecordNum)/2);
				cout << firstRecordNum << "<--- First |( Current: "<< currentRecordNum<<")| Last --->" << lastRecordNum << endl;
				cout << "Grab record from: " << searchPage << endl;
				for (int i = 0; i <= currentRecordNum; i++){
					searchPage->GetFirst(&temp);
				}
				cout << "Got Central Record" << endl;
				int ternary = ce.Compare(&temp, &literal, query);
				cout << "Comparing" << endl;
				exit(1);
				if(ternary == -1) lastRecordNum = currentRecordNum;
				else if(ternary == 1) firstRecordNum = currentRecordNum;
				else{
					cout << "Matching Record Found" << endl;
					found = true;
					fetchMe->Consume(&temp);
					return 1;
				}
			}
		}
	}
	if(found){
		if(currentRecordNum + 1 == searchPage->GetNumRecs()){
			if(currentPageNum + 1 == file.GetLength()){
				cout << "Search Failed:  End of file" << endl;
				fail = true;
				return 0;
			}
			file.GetPage(searchPage, ++currentPageNum);
			currentRecordNum = 0;
			temp.Copy(searchPage->myRecs->Current(currentRecordNum));
			int ternary = ce.Compare(&temp, &literal, query);
			if(ternary != 0){
				cout << "Search Failed: Record doesn't match literal" << endl;
				fail = true;
				return 0;
			}
			else{
				cout << "Matching Record Found" << endl;
				fetchMe->Consume(&temp);
				return 1;
			}
		}
	}

}


int DBFile::TreeFile::GetNext(Record &fetchMe, CNF &applyMe, Record &literal){
	exit(1);
}


int DBFile::Create(char *name, ftype myType, void *startup){
	 if      (myType == heap)   dataFile = (DataFile *)new HeapFile();
	 else if (myType == sorted) dataFile = (DataFile *)new SortFile();
	 else if (myType == tree)   dataFile = (DataFile *)new TreeFile();
	 else HandleError(INVALID_FTYPE);
	 dataFile->Create(name, startup);
	 //dataFile->AddPageAndUpdatePtr();
	 return 1;
}
void DBFile::DataFile::CreateTopOfFile(){
     // Add dummy page and set to Top
	 currentPg = new Page();
	 currentPgNum = 0;
	 pgCount = 0;
}
void DBFile::DataFile::CreateSetup(char *name){
     // This is creating the new file. Note how the length is 0 to do so.
	 file.Open(CREATE_FILE, name);
	 cout << file.GetLength() << endl;
	 CreateTopOfFile();
	 //AddPageAndUpdatePtr();
}
void DBFile::HeapFile::Create(char *name, void *startup){
	DataFile::CreateSetup(name);
	dbType = heap;
	CreateHeader(name, startup);
}
void DBFile::SortFile::Create(char *name, void *startup){
	DataFile::CreateSetup(name);
	dbType = sorted;
	CreateHeader(name, startup);

}
void DBFile::TreeFile::Create(char *name, void *startup){
	DataFile::CreateSetup(name);
	dbType = tree;
	exit(1);
}
void DBFile::DataFile::AddPageAndUpdatePtr(){
	 file.AddPage(currentPg, pgCount++);
     currentPg = new Page();
     ++currentPgNum;
}

char *DBFile::CreateNameHeader(const char *name){
	cout << "Creating HeaderFile Name" << endl;
	char * header = (char *)calloc((strlen(name) + 8),sizeof(char));
	strncat(header, name, strlen(name)*sizeof(char));
	strncat(header, ".header", 7);
	return header;
}
char *DBFile::DataFile::CreateNameHeader(const char *name){
	cout << "Creating FileName Header DATAFILE" << endl;
	char * header = (char *)calloc((strlen(name) + 8), sizeof(char));
	strncat(header, name, strlen(name));
	strncat(header, ".header", 7);
	return header;
}
char *DBFile::SortFile::CreateSortNameHeader(const char *name){

}
void DBFile::HeapFile::CreateHeader(const char *name, void *startup){
    char * headerName = DataFile::CreateNameHeader(name);
	FILE *headerFile = fopen(headerName, "w");
	if(headerFile == NULL) { cout << "OOPS headerBroke!" <<endl; exit(1); }
	else{
		Serial *serial = (Serial *)malloc(sizeof(Serial));
		serial->dbType = dbType;
		serial->runLength = NULL;
		serial->numAtts = NULL;
		int i = 0;
		for(i; i < MAX_ANDS; i++){
			serial->whichAtts[i] = NULL;
			serial->whichTypes[i] = String;
		}
		fwrite((const void *)serial, sizeof(serial),1, headerFile);
	}
	fclose(headerFile);
}
void DBFile::SortFile::CreateHeader(const char *name, void *startup){
	char * headerName = DataFile::CreateNameHeader(name);
	FILE *headerFile;
	headerFile = fopen(headerName, "w");
	if(headerFile == NULL) { cout << "OOPS writeHeaderBroke!" <<endl; exit(1); }
	else{
		int buffer[2*MAX_ANDS + 3];
		SortOrder *order = (SortOrder *)startup;
		buffer[0] = (int)dbType;
		buffer[1] = order->runLength;
		buffer[2] = order->maker->numAtts;
		int i = 0;
		for(i; i<MAX_ANDS; i++){
			buffer[i+3] = order->maker->whichAtts[i];
		}
		int j = 0;
		for(j;j<MAX_ANDS; j++){
			buffer[i + j + 3] = (int)(order->maker->whichTypes[j]);
		}
		fwrite((const void *) buffer, sizeof(int), 2*MAX_ANDS + 3, headerFile);
	}
	fclose(headerFile);
	free(headerName);
	cout << "Header File Created" << endl;
}
void DBFile::TreeFile::CreateHeader(const char *name, void *startup){
	exit(1);
}

void DBFile::ReadHeader(const char *name){
	 cout << "Reading header Page" << endl;
	 char *headerName = CreateNameHeader(name);
	 FILE *headerFile;
	 if((headerFile = fopen(headerName, "r")) == NULL){ cout << "OOPS readHeaderBroke!" << endl; exit(1); }
	 else{
		 int buffer[2*MAX_ANDS + 3] = {0};
		 fread((void *)buffer, sizeof(int), 2*MAX_ANDS + 3, headerFile);
		 fclose(headerFile);
		 if     (buffer[0] == heap)   dataFile = (DataFile *)new HeapFile();
		 else if(buffer[0] == tree)   dataFile = (DataFile *)new TreeFile();
		 else if(buffer[0] == sorted){
			 cout << "This is a sorted file" << endl;
			 dataFile = (DataFile *)new SortFile();
			 ((SortFile *)dataFile)->ReadSorted(buffer);
		 }else{
			 cout << "OOPS!! REALLY BAD PROBLEM... Type: "<< buffer[0]<< endl;
			 exit(1);
		 }
	 }
	 free(headerName);
	 cout << "Done Reading header page" << endl;
 }
void DBFile::SortFile::ReadSorted(int buffer[]){

	runLength = buffer[1];
	sortOrder = new OrderMaker();
	sortOrder->numAtts = buffer[2];
        int i = 0;
        for (i; i < MAX_ANDS; i++){
		sortOrder->whichAtts[i] = buffer[i + 3];
	}
        int j = 0;
	for(j;j<MAX_ANDS; j++){
		sortOrder->whichTypes[j]= (Type)buffer[i+j+2];
	}
}
void DBFile::SortFile::BigQSetup(){
	cout << "BigQ Setup" << endl;
	sorter = new BigQ(*sortOrder, runLength);
	writeEnabled = true;
}
void DBFile::SortFile::BigQShutdown(){
	writeEnabled = false;
	if(currentPgNum > 1) MoveFirst();
	CommitTransaction();
	BigQCleanup();
}
void DBFile::SortFile::CommitTransaction(){
	cout << "Committing Transaction ... "<< endl;
	Record tempA, tempB;
	int pageSize = 0;
	pgCount = 0;
	while(GetNext(tempB)){
		sorter->AddToPipe(&tempB);
	}
    sorter->CloseInPipe();
    while (sorter->GetNext(&tempA)) if(!currentPg->Append(&tempA)) TryAddPageAndRecord(&tempA);
    cout << currentPg->GetNumRecs()<< endl;
    file.AddPage(currentPg, ++pageSize);
	cout << "Transaction Committed" << endl;
}
void DBFile::SortFile::Rollback(){
}
void DBFile::SortFile::BigQCleanup(){
	delete sorter;
}

int DBFile::Open(char *name){
	 ReadHeader(name);
	 dataFile->Open(name);
}
int DBFile::DataFile::Open(char *name){
	 cout << name << endl;
	 file.Open(OPEN_FILE, name);
	 cout <<file.GetLength() <<endl;
	 currentPg = new Page();
	 currentPgNum = 1;
	 //AddPageAndUpdatePtr();
	 pgCount = file.GetLength();
         cout << "DONE OPENING FILE" << endl;

}


int DBFile::DataFile::TryAddPageAndRecord(Record &temp){
	 AddPageAndUpdatePtr();
	 return (*currentPg).Append(&temp);
}

int DBFile::Close(){
	 dataFile->Close();
}
int DBFile::HeapFile::Close(){
	 file.Close();
     cout << "File Closed." << endl;
	 return 1;
}
int DBFile::SortFile::Close(){
	cout << "Closing" << endl;
	cout << writeEnabled << endl;
	if(writeEnabled) BigQShutdown();
	cout << file.Close() << endl;
	cout << "File Closed." << endl;
}
int DBFile::TreeFile::Close(){
	exit(1);
}

void DBFile::Load(Schema &mySchema, char *loadMe){
     dataFile->Load(mySchema, loadMe);
}
void DBFile::HeapFile::Load(Schema &mySchema, char *loadMe){
	FILE *tblFile = fopen(loadMe, "r");
	Record temp;
    while(temp.SuckNextRecord(&mySchema, tblFile)) if(!Add(temp)) break;
    file.AddPage(currentPg, pgCount);
    fclose(tblFile);
}
void DBFile::SortFile::Load(Schema &mySchema, char *loadMe){
     if(!writeEnabled) BigQSetup();
     FILE *tblFile = fopen(loadMe, "r");
     Record temp;
     while(temp.SuckNextRecord(&mySchema, tblFile)) if(!Add(temp)) break;
     fclose(tblFile);
}
void DBFile::TreeFile::Load(Schema &mySchema, char *loadMe){
     exit(1);
}

void DBFile::HandleError(int errorCode){
     cout << ((errorCode == INVALID_FTYPE)? "Oops, invalid ftype param. Fail-Fast.": "UNKNOWN ERROR. Fail-Fast.") << endl;
     exit(1);
}

